// Copyright (C) <2019> Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0

#include <iostream>

#include <ext_list.hpp>

#include "common.h"

using namespace std;
using namespace InferenceEngine;

void __now() {
    char buf[128];
    timeval curTime;
    gettimeofday(&curTime, NULL);

    time_t time;
    time = curTime.tv_sec;

    struct tm* p_time = localtime(&time);
    strftime(buf, 128, "%Y-%m-%d %H:%M:%S", p_time);

    std::cout << buf << "." << std::setw(3) << std::setfill('1') << curTime.tv_usec / 1001;
}

bool loadModel(std::string m_device, std::string m_model, InferencePlugin *m_plugin, InferRequest *m_infer_request, Blob::Ptr *m_input_blob, Blob::Ptr *m_output_blob) {
    const InferenceEngine::Version *version = GetInferenceEngineVersion();
    cout << "InferenceEngineVersion: " << endl;
    cout << "\tAPI version .......... " << version->apiVersion.major << "." << version->apiVersion.minor << endl;
    if (nullptr != version->buildNumber)
        cout << "\tBuild ................ " << version->buildNumber << endl;
    if (nullptr != version->description)
        cout << "\tDescription .......... " << version->description << endl;

    // --------------------------- Load Plugin for inference engine -------------------------------------
    cout << "Loading plugin" << endl;
    *m_plugin = PluginDispatcher({"" }).getPluginByDevice(m_device);

    /*If CPU device, load default library with extensions that comes with the product*/
    if (m_device.find("CPU") != std::string::npos) {
        /**
         * cpu_extensions library is compiled from "extension" folder containing
         * custom MKLDNNPlugin layer implementations. These layers are not supported
         * by mkldnn, but they can be useful for inferring custom topologies.
         **/
        m_plugin->AddExtension(std::make_shared<Extensions::Cpu::CpuExtensions>());
    }

    /** Printing plugin version **/
    const InferenceEngine::Version *pluginVersion = nullptr;
    static_cast<InferenceEngine::InferenceEnginePluginPtr>(*m_plugin)->GetVersion(pluginVersion);
    cout << "PluginVersion: " << endl;
    cout << "\tAPI version .......... " << pluginVersion->apiVersion.major << "." << pluginVersion->apiVersion.minor << endl;
    if (nullptr != pluginVersion->buildNumber)
        cout << "\tBuild ................ " << pluginVersion->buildNumber << endl;
    if (nullptr != pluginVersion->description)
        cout << "\tDescription .......... " << pluginVersion->description << endl;
    // -----------------------------------------------------------------------------------------------------

    // --------------------------- Read IR Generated by ModelOptimizer (.xml and .bin files) ------------
    std::string binFileName = m_model;
    binFileName.replace(binFileName.find(".xml"), sizeof(".xml"), ".bin");

    cout << "Loading network files:"
        "\n\t" << binFileName <<
        endl;

    /** Read network model **/
    CNNNetReader networkReader;
    networkReader.ReadNetwork(m_model);

    /** Extract model name and load weights **/
    networkReader.ReadWeights(binFileName);
    CNNNetwork network = networkReader.getNetwork();

    cout << "\t" << "Precision: " << network.getPrecision() << endl;
    cout << "\t" << "Layer count: " << network.layerCount() << endl;
    cout << "\t" << "Batch size: " << network.getBatchSize() << endl;
    // -----------------------------------------------------------------------------------------------------

    // --------------------------- Prepare input blobs --------------------------------------------------
    cout << "Preparing input blobs" << endl;

    /** Taking information about all topology inputs **/
    InputsDataMap inputsInfo(network.getInputsInfo());
    if (inputsInfo.size() != 1) throw std::logic_error("Sample supports topologies only with 1 input");

    std::string imageInputName, imInfoInputName;
    imageInputName = inputsInfo.begin()->first;
    cout << "imageInputName: " << imageInputName << endl;

    InputInfo::Ptr inputInfo = inputsInfo.begin()->second;
    if (inputInfo->getInputData()->getTensorDesc().getDims().size() != 4) throw std::logic_error("Sample supports input only with dims 4");
    for (size_t i = 0; i < inputInfo->getInputData()->getTensorDesc().getDims().size(); i++) {
        cout << "\tDims[" << i << "]: " << inputInfo->getInputData()->getTensorDesc().getDims()[i]<< endl;
    }

    //int modelInputSize = inputInfo->getInputData()->getTensorDesc().getDims()[3];

    /** Creating first input blob **/
    Precision inputPrecision = Precision::U8;
    inputInfo->setPrecision(inputPrecision);
    cout << "\tPrecision: " << inputPrecision << endl;
    // -----------------------------------------------------------------------------------------------------

    // --------------------------- Prepare output blobs -------------------------------------------------
    cout << "Preparing output blobs" << endl;

    OutputsDataMap outputsInfo(network.getOutputsInfo());
    if (outputsInfo.size() != 1) throw std::logic_error("Sample supports topologies only with 1 output");

    std::string outputName;
    DataPtr outputInfo;
    for (const auto& out : outputsInfo) {
        outputName = out.first;
        outputInfo = out.second;

        cout << "outputName: " << out.first << endl;

        for (size_t i = 0; i < outputInfo->getTensorDesc().getDims().size(); i++) {
            cout << "\tDims[" << i << "]: " << outputInfo->getTensorDesc().getDims()[i] << endl;
        }

        /** Set the precision of output data provided by the user, should be called before load of the network to the plugin **/
        Precision outputPrecision;
        outputPrecision = Precision::FP32;
        outputInfo->setPrecision(outputPrecision);
        cout << "\tPrecision: " << outputPrecision << endl;
    }
    // -----------------------------------------------------------------------------------------------------

    // --------------------------- Loading model to the plugin ------------------------------------------
    cout << "Loading model to the plugin" << endl;

    ExecutableNetwork executable_network = m_plugin->LoadNetwork(network, {});
    // -----------------------------------------------------------------------------------------------------

    // --------------------------- Create infer request -------------------------------------------------
    *m_infer_request = executable_network.CreateInferRequest();

    /** Creating blob **/
    *m_input_blob = m_infer_request->GetBlob(imageInputName);
    *m_output_blob = m_infer_request->GetBlob(outputName);

    return true;
}

